#pragma once
#include <d3d12.h>
#include "../Macros.h"
#include "../Libs.h"
#include "../Window.h"
#include "Selah.h"
namespace SL
{

class Backend
{
public:
    // Simple free list based allocator
    struct ExampleDescriptorHeapAllocator
    {
        ID3D12DescriptorHeap* Heap = nullptr;
        D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES;
        D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu;
        D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu;
        UINT HeapHandleIncrement;
        std::vector<int> FreeIndices;

        void Create(ID3D12Device* device, ID3D12DescriptorHeap* heap)
        {
            assert(Heap == nullptr && FreeIndices.empty());
            Heap = heap;
            D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc();
            HeapType = desc.Type;
            HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart();
            HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart();
            HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType);
            FreeIndices.reserve((int)desc.NumDescriptors);
            for (int n = desc.NumDescriptors; n > 0; n--)
                FreeIndices.push_back(n);
        }
        void Destroy()
        {
            Heap = nullptr;
            FreeIndices.clear();
        }
        void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle)
        {
            IM_ASSERT(FreeIndices.size() > 0);
            int idx = FreeIndices.back();
            FreeIndices.pop_back();
            out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement);
            out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement);
        }
        void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle)
        {
            int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) / HeapHandleIncrement);
            int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) / HeapHandleIncrement);
            IM_ASSERT(cpu_idx == gpu_idx);
            FreeIndices.push_back(cpu_idx);
        }
    };

public:
    Backend();
    ~Backend();

    void Init(AppConfig& config, Window& window);
    void Shutdown();
    void PreRender();
    void PostRender();
    // void Resize(int width, int height);

    struct DX12_context
    {
        ID3D12Device* device;
        IDXGIFactory4* factory;
        IDXGISwapChain3* swapChain;
        ID3D12CommandQueue* graphicsQueue;
        ID3D12CommandQueue* computeQueue;
        ID3D12CommandQueue* copyQueue;
        ID3D12CommandQueue* bundleQueue;
        ID3D12CommandAllocator* commandAllocator;
        ID3D12Resource* mainRTV;
        ID3D12Resource* mainDSV;
    };

    DX12_context& GetContext() { return m_context; }
    ID3D12GraphicsCommandList* CreateCommandList(){
        return createCommandList();
    }

private:
    AppConfig m_config;
    DX12_context m_context;

    bool createInstance();
    bool createSwapchain(GLFWwindow* window);
    bool createDevice();
    bool createCommandQueues();
    bool createFrameResource();
    bool createDescriptorHeap();
    ID3D12GraphicsCommandList* createCommandList();
    //(root signature)
    //(pipeline state object)
};

} // namespace SL